avatar

目录
275 - getter and setter

275 - Getter and Setter

好的,同学你好!今天我们来学习 C++ 中非常重要的一个概念:Getters 和 Setters。这节课我会带你一步一步理解它们,并学会如何在代码中使用它们。

C++ 课堂:理解 Getters 和 Setters (访问器与修改器)

总结: Getters 和 Setters 是一组成对的成员函数 (member functions),它们分别用于读取(获取)和修改(设置)类 (class) 中私有 (private) 成员变量 (member variables) 的值。这是实现数据封装 (encapsulation) 的关键手段之一,能让我们更好地控制对类内部数据的访问。


1. 详细解释

1.1 为什么需要 Getters 和 Setters?

在面向对象编程中,一个核心思想是封装 (encapsulation)。这意味着我们通常会将类的成员变量声明为 private,以防止它们被类外部的代码直接随意修改,从而保护数据的完整性和一致性。

想象一下,你有一个 Cylinder(圆柱体)类,它有 base_radius_(底面半径)和 height_(高)两个私有成员变量。

C++

Code
1
2
3
4
5
6
class Cylinder {
private:
double base_radius_;
double height_;
// ... 其他成员
};

如果这些变量是 public(公有的),那么在类的外部(比如 main 函数中),任何人都可以直接这样写:

C++

Code
1
2
3
4
// 假设 base_radius_ 和 height_ 是 public 的 (不推荐)
Cylinder c1;
c1.base_radius_ = -5.0; // 半径可以是负数吗?这显然不合理!
c1.height_ = 0.0; // 高度为0?可能也不符合预期。

这种直接访问和修改的方式,使得我们无法对赋给成员变量的值进行任何检查或控制。例如,半径不应该是负数。如果成员变量是 private 的,外部代码就不能直接访问它们,这就保护了它们。

但问题来了:如果成员变量是 private 的,我们又确实需要在类的外部读取它们的值,或者在满足某些条件的情况下修改它们的值,该怎么办呢?

答案就是使用 GettersSetters

1.2 Getters (获取器/访问器)

  • 用途:Getter 函数用于从类的外部读取私有成员变量的值。它“获取”数据。
  • 特点
    • 通常是 public 的,这样外部才能调用它。
    • 它的返回类型 (return type) 与它要获取的成员变量的类型相同。
    • 通常不接受任何参数 (parameters)。
    • 函数体内部很简单,就是 return 相应的私有成员变量。
    • 一个好的实践是,如果 Getter 不会修改任何成员变量(通常它们不会),应该将它们声明为 const 成员函数。这意味着这个函数可以在一个 const 对象上被调用。

示例 (续上例)

C++

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Cylinder {
private:
double base_radius_;
double height_;
public:
// 构造函数 (Constructor)
Cylinder(double r, double h) : base_radius_(r), height_(h) {}

// Getter for base_radius_
double get_base_radius() const { // 注意 const
return base_radius_;
}

// Getter for height_
double get_height() const { // 注意 const
return height_;
}
// ... 其他成员
};

现在,在 main 函数中,我们可以这样做:

C++

Code
1
2
3
4
Cylinder c1(10.0, 5.0);
double radius = c1.get_base_radius(); // 正确:通过 getter 获取半径
double h = c1.get_height(); // 正确:通过 getter 获取高度
// c1.base_radius_ = 2.0; // 错误!base_radius_ 是 private 的

1.3 Setters (设置器/修改器)

  • 用途:Setter 函数用于从类的外部修改私有成员变量的值。它“设置”数据。
  • 特点
    • 通常是 public 的。
    • 它的返回类型通常是 void,因为它只是修改数据,不需要返回什么。
    • 它通常接受一个参数,该参数的类型与它要设置的成员变量的类型相同。这个参数将是新的值。
    • 函数体内部会将传入的参数赋值给相应的私有成员变量。
    • 非常重要的一点:Setter 允许我们在赋值之前加入验证逻辑。这是 Setter 相比直接公开成员变量的一大优势。

示例 (续上例)

C++

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class Cylinder {
private:
double base_radius_;
double height_;
public:
// 构造函数
Cylinder(double r, double h) {
// 构造函数中也可以使用setter进行初始化,以复用验证逻辑
set_base_radius(r);
set_height(h);
}

// Getter for base_radius_
double get_base_radius() const {
return base_radius_;
}

// Getter for height_
double get_height() const {
return height_;
}

// Setter for base_radius_
void set_base_radius(double r_param) {
if (r_param > 0) { // 验证逻辑:半径必须大于0
base_radius_ = r_param;
} else {
// 可以选择报错、设置一个默认值或什么都不做
// 这里我们简单地不修改,或者可以抛出异常
// std::cerr << "Error: Radius must be positive." << std::endl;
base_radius_ = 1.0; // 或者赋一个默认值
}
}

// Setter for height_
void set_height(double h_param) {
if (h_param > 0) { // 验证逻辑:高度必须大于0
height_ = h_param;
} else {
// std::cerr << "Error: Height must be positive." << std::endl;
height_ = 1.0; // 或者赋一个默认值
}
}

double volume() const {
return 3.1415926535 * base_radius_ * base_radius_ * height_;
}
};

现在,在 main 函数中,我们可以这样做:

C++

Code
1
2
3
4
5
6
7
8
Cylinder c1(10.0, 5.0);
std::cout << "Initial radius: " << c1.get_base_radius() << std::endl;

c1.set_base_radius(12.0); // 正确:通过 setter 修改半径
std::cout << "New radius: " << c1.get_base_radius() << std::endl;

c1.set_base_radius(-2.0); // 尝试设置一个无效的半径
std::cout << "Radius after invalid set: " << c1.get_base_radius() << std::endl; // 半径会是1.0(默认值)或保持不变,取决于setter的实现

1.4 命名约定

虽然不是强制的,但 Getter 通常以 get 开头,后跟成员变量名(首字母大写),例如 get_base_radius() 或 getBaseRadius()。

Setter 通常以 set 开头,后跟成员变量名(首字母大写),例如 set_base_radius(double r) 或 setBaseRadius(double r)。

这种约定使得代码更易读。

1.5 访问控制 (public Vs private)

正如视频中提到的,Getters 和 Setters 必须是 public 的,这样它们才能从类的外部被调用。如果它们被声明为 private,那么它们就只能在类的内部被其他成员函数调用,这对于从外部控制数据的目的来说是无效的,并且会导致编译器错误,就像视频中演示的那样。

public: 关键字之后声明的所有成员(直到下一个访问说明符如 private: 或 protected: 或类定义结束)都是公有的。

private: 关键字之后声明的所有成员都是私有的。


2. 代码示例 (Cylinder 类)

让我们来看一个完整的 Cylinder 类的例子,它使用了构造函数、Getters、Setters 和一个计算体积的方法 (method)。

C++

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#include <iostream>
#include <cmath> // 为了 M_PI (有些编译器需要 #define _USE_MATH_DEFINES)

// 如果 M_PI 未定义 (例如在 MSVC 中且未 #define _USE_MATH_DEFINES)
#ifndef M_PI
#define M_PI (3.14159265358979323846)
#endif

class Cylinder {
private:
double base_radius_; // 私有成员变量 (private member variable)
double height_; // 私有成员变量

public:
// 构造函数 (Constructor)
Cylinder(double r_param, double h_param) {
// 在构造函数中使用 setters 可以复用验证逻辑
set_base_radius(r_param);
set_height(h_param);
std::cout << "Cylinder object created with radius " << base_radius_
<< " and height " << height_ << std::endl;
}

// 默认构造函数
Cylinder() : base_radius_(1.0), height_(1.0) {
std::cout << "Default Cylinder object created with radius 1.0 and height 1.0" << std::endl;
}

// Getter for base_radius_
double get_base_radius() const { // const 表示此方法不会修改对象的状态
return base_radius_;
}

// Setter for base_radius_
// 参数 (parameter) : rad - a double representing the new radius
void set_base_radius(double rad) {
if (rad > 0) {
base_radius_ = rad;
} else {
std::cout << "Invalid radius: " << rad << ". Radius must be positive. Setting to 1.0." << std::endl;
base_radius_ = 1.0; // 设置一个默认的有效值
}
}

// Getter for height_
double get_height() const {
return height_;
}

// Setter for height_
// 参数 : h - a double representing the new height
void set_height(double h) {
if (h > 0) {
height_ = h;
} else {
std::cout << "Invalid height: " << h << ". Height must be positive. Setting to 1.0." << std::endl;
height_ = 1.0; // 设置一个默认的有效值
}
}

// Method to calculate volume
double volume() const {
return M_PI * base_radius_ * base_radius_ * height_;
}
};

int main() {
// 创建一个 Cylinder 对象 (object/instance)
Cylinder cylinder1(10.0, 2.0);
std::cout << "Initial volume: " << cylinder1.volume() << std::endl;
std::cout << "Initial radius (via getter): " << cylinder1.get_base_radius() << std::endl;
std::cout << "Initial height (via getter): " << cylinder1.get_height() << std::endl;

std::cout << "\n--- Modifying cylinder1 ---" << std::endl;
// 使用 setters 修改成员变量
cylinder1.set_base_radius(5.0);
cylinder1.set_height(3.0);

std::cout << "New radius (via getter): " << cylinder1.get_base_radius() << std::endl;
std::cout << "New height (via getter): " << cylinder1.get_height() << std::endl;
std::cout << "Volume after modification: " << cylinder1.volume() << std::endl;

std::cout << "\n--- Attempting invalid modifications ---" << std::endl;
cylinder1.set_base_radius(-2.0); // 尝试设置无效的半径
cylinder1.set_height(0); // 尝试设置无效的高度

std::cout << "Radius after invalid set: " << cylinder1.get_base_radius() << std::endl;
std::cout << "Height after invalid set: " << cylinder1.get_height() << std::endl;
std::cout << "Volume after invalid attempts: " << cylinder1.volume() << std::endl;

// 尝试直接访问 (会导致编译错误,因为它们是 private)
// cylinder1.base_radius_ = 7.0; // 错误: 'double Cylinder::base_radius_' is private
// double r = cylinder1.height_; // 错误: 'double Cylinder::height_' is private

// 使用 const 对象调用 const getter
const Cylinder cylinder2(3.0, 4.0);
std::cout << "\n--- Constant Cylinder ---" << std::endl;
std::cout << "Const cylinder radius: " << cylinder2.get_base_radius() << std::endl;
std::cout << "Const cylinder height: " << cylinder2.get_height() << std::endl;
std::cout << "Const cylinder volume: " << cylinder2.volume() << std::endl;
// cylinder2.set_base_radius(5.0); // 错误! 不能在 const 对象上调用非 const 方法 (setter)

return 0;
}

编译和运行上述代码,你会看到:

  1. 对象创建时构造函数被调用。
  2. volume() 函数根据初始值计算体积。
  3. 通过 set_base_radius()set_height() 修改了对象的属性后,volume() 计算出的值也相应改变。
  4. 当你尝试用 set_base_radius(-2.0) 设置一个无效值时,setter 函数内的逻辑会阻止无效赋值,并可能设置一个默认值,从而保护了对象的内部状态。
  5. const 对象只能调用 const 成员函数 (比如我们的 getters 和 volume() 方法)。

3. 问答卡片 (QA Flash Cards)

  1. 问: 什么是 Getter?它有什么作用?

    答: Getter 是一个公有成员函数,用于从类外部安全地读取私有成员变量的值。它通常以 get 开头,返回成员变量的值,并且最好声明为 const。

  2. 问: 什么是 Setter?它有什么作用?

    答: Setter 是一个公有成员函数,用于从类外部安全地修改私有成员变量的值。它通常以 set 开头,接受一个参数作为新值,返回类型通常为 void。Setter 可以在赋值前进行数据验证。

  3. 问: 为什么不直接将成员变量设为 public,而是使用 Getters 和 Setters?

    答: 为了实现封装。将成员变量设为 private 并通过 public 的 Getters/Setters 访问,可以:

    • 控制访问级别:只提供 Getter 不提供 Setter,可以创建只读属性。
    • 数据验证:Setter 可以在修改数据前检查数据是否有效。
    • 隐藏实现细节:类的内部实现可以改变,只要 Getters/Setters 的接口不变,外部代码就不受影响。
    • 灵活性:未来可能需要在获取或设置值时执行额外操作(如日志记录、通知等)。
  4. 问: Getter 和 Setter 函数通常声明在类的哪个访问区域?

    答: public 区域,这样它们才能从类的外部被调用。

  5. 问: Getter 函数通常应该标记为什么,以表明它不修改类的状态?

    答: const。


4. 常见误解或易犯错误

  1. 将 Getters/Setters 声明为 private

    • 误解:以为所有成员函数都可以随意设置访问权限。
    • 后果:如果 Getters/Setters 是 private 的,它们就不能从类外部被调用,失去了其作为公共接口的意义,会导致编译错误。
  2. Getter 函数修改了成员变量的值

    • 误解:Getter 只是一个普通函数,可以在里面做任何事。
    • 后果:Getter 的设计目的是“获取”数据,不应该有副作用,特别是修改对象状态。这违反了“最少意外原则”。正确的做法是将其声明为 const
  3. Setter 函数忘记接收参数,或者参数类型不匹配

    • 误解:写函数时马虎。
    • 后果:Setter 需要一个参数来指定新的值。如果忘记参数或类型不符,会导致编译错误。
  4. 忘记调用函数时的括号 ()

    • 错误double r = cylinder.get_base_radius; (缺少 ())
    • 正确double r = cylinder.get_base_radius();
    • 后果:如果是获取函数地址(在某些情况下可能),而不是调用函数,会导致逻辑错误或编译错误。
  5. 认为 Getter 返回的是原始数据的引用 (reference),除非明确指出

    • 误解:以为 get_base_radius() 返回的就是 base_radius_ 本身。
    • 后果:默认情况下,Getter 按值返回 (return by value),这意味着返回的是成员变量的一个副本 (copy)。如果想返回引用(需要谨慎使用),函数签名会是 double& get_base_radius()。视频中的例子是按值返回,这是最常见和安全的方式。
  6. 在 Setter 中未进行任何数据验证

    • 后果:虽然技术上可行,但这样做就失去了 Setter 的一个主要优势。如果 Setter 只是简单赋值 member_ = value;,那它和直接公开成员变量的区别就小了很多(尽管仍然提供了未来添加逻辑的可能)。

5. 编码练习

现在,我们来做一个小练习。下面有一个 Rectangle(矩形)类,它有两个私有成员变量:width_ (宽度) 和 height_ (高度)。请你为这个类完成 Getters 和 Setters,并在 main 函数中测试它们。

C++

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include <iostream>

class Rectangle {
private:
double width_;
double height_;

public:
// 构造函数
Rectangle(double w, double h) {
// TODO: 使用你将要编写的 setters 来初始化 width_ 和 height_
// 确保宽度和高度是正数,如果不是,则都设置为 1.0
set_width(w);
set_height(h);
std::cout << "Rectangle created with width: " << width_ << " and height: " << height_ << std::endl;
}

// Getter for width_
// TODO: 实现 get_width() 方法
// 它应该返回 width_ 的值,并且是一个 const 方法
double get_width() const {
// 请在此处填入代码
return 0.0; // 这是一个占位符,你需要修改它
}

// Setter for width_
// TODO: 实现 set_width() 方法
// 它应该接收一个 double 类型的参数 new_width
// 如果 new_width 大于 0,则将其赋值给 width_
// 否则,打印错误消息并将 width_ 设置为 1.0
void set_width(double new_width) {
// 请在此处填入代码
std::cout << "Placeholder for set_width. Width not set." << std::endl; // 这是一个占位符
}

// Getter for height_
// TODO: 实现 get_height() 方法
// 它应该返回 height_ 的值,并且是一个 const 方法
double get_height() const {
// 请在此处填入代码
return 0.0; // 这是一个占位符,你需要修改它
}

// Setter for height_
// TODO: 实现 set_height() 方法
// 它应该接收一个 double 类型的参数 new_height
// 如果 new_height 大于 0,则将其赋值给 height_
// 否则,打印错误消息并将 height_ 设置为 1.0
void set_height(double new_height) {
// 请在此处填入代码
std::cout << "Placeholder for set_height. Height not set." << std::endl; // 这是一个占位符
}

// 计算面积的方法
double area() const {
return width_ * height_;
}
};

int main() {
std::cout << "--- Creating r1 (valid values) ---" << std::endl;
Rectangle r1(10.0, 5.0); // 应该使用 setters 初始化
std::cout << "r1 initial width: " << r1.get_width() << std::endl;
std::cout << "r1 initial height: " << r1.get_height() << std::endl;
std::cout << "r1 initial area: " << r1.area() << std::endl;

std::cout << "\n--- Modifying r1 ---" << std::endl;
r1.set_width(7.0);
r1.set_height(3.0);
std::cout << "r1 new width: " << r1.get_width() << std::endl;
std::cout << "r1 new height: " << r1.get_height() << std::endl;
std::cout << "r1 new area: " << r1.area() << std::endl;

std::cout << "\n--- Creating r2 (invalid values for constructor) ---" << std::endl;
Rectangle r2(-2.0, 0.0); // 构造函数中的 setters 应该处理这些无效值
std::cout << "r2 initial width: " << r2.get_width() << std::endl;
std::cout << "r2 initial height: " << r2.get_height() << std::endl;
std::cout << "r2 initial area: " << r2.area() << std::endl;

std::cout << "\n--- Attempting invalid set on r1 ---" << std::endl;
r1.set_width(-5.0);
std::cout << "r1 width after invalid set: " << r1.get_width() << std::endl;
std::cout << "r1 area after invalid width set: " << r1.area() << std::endl;

return 0;
}

请你将上面代码中标记为 // 请在此处填入代码// TODO: 的部分补充完整。完成后,将你的代码粘贴给我,我会帮你检查并给出反馈!加油!

c++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#include <iostream>

class Rectangle {
private:
double width_;
double height_;

public:
// 构造函数
Rectangle(double w, double h) {
// TODO: 使用你将要编写的 setters 来初始化 width_ 和 height_
// 确保宽度和高度是正数,如果不是,则都设置为 1.0
set_width(w);
set_height(h);
std::cout << "Rectangle created with width: " << width_ << " and height: " << height_ << std::endl;
}

// Getter for width_
// TODO: 实现 get_width() 方法
// 它应该返回 width_ 的值,并且是一个 const 方法
double get_width() const {
// 请在此处填入代码
return width_; // 这是一个占位符,你需要修改它
}

// Setter for width_
// TODO: 实现 set_width() 方法
// 它应该接收一个 double 类型的参数 new_width
// 如果 new_width 大于 0,则将其赋值给 width_
// 否则,打印错误消息并将 width_ 设置为 1.0
void set_width(double new_width) {
// 请在此处填入代码
if (new_width > 0)
{
width_ = new_width;
}
else
{
width_ = 1;
std::cout << "Width Not set. Width should be greater than 0. Width is set to default 1.0." << std::endl; // 这是一个占位符
}

}

// Getter for height_
// TODO: 实现 get_height() 方法
// 它应该返回 height_ 的值,并且是一个 const 方法
double get_height() const {
// 请在此处填入代码
return height_; // 这是一个占位符,你需要修改它
}

// Setter for height_
// TODO: 实现 set_height() 方法
// 它应该接收一个 double 类型的参数 new_height
// 如果 new_height 大于 0,则将其赋值给 height_
// 否则,打印错误消息并将 height_ 设置为 1.0
void set_height(double new_height) {
// 请在此处填入代码
if (new_height > 0)
{
height_ = new_height;
}
else
{
height_ = 1;
std::cout << "Height Not set. Height should be greater than 0. Height is set to default 1.0." << std::endl; // 这是一个占位符
}

}

// 计算面积的方法
double area() const {
return width_ * height_;
}
};

int main() {
std::cout << "--- Creating r1 (valid values) ---" << std::endl;
Rectangle r1(10.0, 5.0); // 应该使用 setters 初始化
std::cout << "r1 initial width: " << r1.get_width() << std::endl;
std::cout << "r1 initial height: " << r1.get_height() << std::endl;
std::cout << "r1 initial area: " << r1.area() << std::endl;

std::cout << "\n--- Modifying r1 ---" << std::endl;
r1.set_width(7.0);
r1.set_height(3.0);
std::cout << "r1 new width: " << r1.get_width() << std::endl;
std::cout << "r1 new height: " << r1.get_height() << std::endl;
std::cout << "r1 new area: " << r1.area() << std::endl;

std::cout << "\n--- Creating r2 (invalid values for constructor) ---" << std::endl;
Rectangle r2(-2.0, 0.0); // 构造函数中的 setters 应该处理这些无效值
std::cout << "r2 initial width: " << r2.get_width() << std::endl;
std::cout << "r2 initial height: " << r2.get_height() << std::endl;
std::cout << "r2 initial area: " << r2.area() << std::endl;

std::cout << "\n--- Attempting invalid set on r1 ---" << std::endl;
r1.set_width(-5.0);
std::cout << "r1 width after invalid set: " << r1.get_width() << std::endl;
std::cout << "r1 area after invalid width set: " << r1.area() << std::endl;

return 0;
}

太棒了!你做得非常好!很高兴看到你把练习代码补充完整了。让我们一起来看看你的实现。


7. 代码评估与解答

评估你的解决方案

整体评价:优秀! (Excellent!)

你非常出色地完成了 Getters 和 Setters 的实现,并且正确地在 Setters 中加入了数据验证逻辑,也在构造函数中复用了 Setters。const 的使用也是正确的。这表明你已经很好地理解了这一节课的核心概念。

具体亮点:

  1. Getters (get_width, get_height)

    • 正确返回了私有成员变量 width_height_
    • 正确地将这两个方法标记为了 const,表明它们不会修改对象状态。完美!
  2. Setters (set_width, set_height)

    • 正确接收了参数。
    • 核心的验证逻辑 if (new_value > 0) 非常清晰且正确。
    • 当输入无效时,你将成员变量设置为了默认值 1,并打印了提示信息。这符合练习的要求。
  3. 构造函数 (Rectangle(double w, double h))

    • 你正确地调用了 set_width(w);set_height(h); 来初始化成员变量。这是一个很好的实践,因为它复用了 Setters 中的验证逻辑,避免了代码重复。

一些小建议(锦上添花):

  1. 浮点数常量:在 Setters 中,当输入无效时,你将 width_height_ 设置为 1。因为 width_height_double 类型,更明确(也更符合类型一致性)的写法是 1.0。虽然编译器通常会自动转换,但显式写出 1.0 可以让代码意图更清晰。

    • 例如:width_ = 1.0;
  2. 错误/提示信息:你打印的提示信息 “Width Not set…” 或 “Height Not set…” 稍微有点歧义,因为实际上你是将它们设置为默认值 1.0 了。可以考虑修改为类似:”Invalid width provided. Width must be positive. Setting to default 1.0.” 这样用户能更准确地理解发生了什么。

这些只是非常小的建议,你的核心逻辑和实现都是非常棒的!

参考答案

下面是根据你的实现和我的小建议调整后的 Rectangle 类代码部分:

C++

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include <iostream>

class Rectangle {
private:
double width_;
double height_;

public:
// 构造函数
Rectangle(double w, double h) {
set_width(w); // 使用 setter 初始化,包含验证逻辑
set_height(h); // 使用 setter 初始化,包含验证逻辑
// 构造函数中的这条输出可以保留用于学习,实际项目中可能会移除
std::cout << "Rectangle created with width: " << width_
<< " and height: " << height_ << std::endl;
}

// Getter for width_
double get_width() const {
return width_;
}

// Setter for width_
void set_width(double new_width) {
if (new_width > 0) {
width_ = new_width;
} else {
std::cout << "Invalid width provided: " << new_width
<< ". Width must be positive. Setting to default 1.0." << std::endl;
width_ = 1.0; // 使用 1.0 强调是 double 类型
}
}

// Getter for height_
double get_height() const {
return height_;
}

// Setter for height_
void set_height(double new_height) {
if (new_height > 0) {
height_ = new_height;
} else {
std::cout << "Invalid height provided: " << new_height
<< ". Height must be positive. Setting to default 1.0." << std::endl;
height_ = 1.0; // 使用 1.0 强调是 double 类型
}
}

// 计算面积的方法
double area() const {
return width_ * height_;
}
};

// main 函数与之前练习中的相同,这里不再重复,用你的 main 函数测试即可
int main() {
std::cout << "--- Creating r1 (valid values) ---" << std::endl;
Rectangle r1(10.0, 5.0);
std::cout << "r1 initial width: " << r1.get_width() << std::endl;
std::cout << "r1 initial height: " << r1.get_height() << std::endl;
std::cout << "r1 initial area: " << r1.area() << std::endl;

std::cout << "\n--- Modifying r1 ---" << std::endl;
r1.set_width(7.0);
r1.set_height(3.0);
std::cout << "r1 new width: " << r1.get_width() << std::endl;
std::cout << "r1 new height: " << r1.get_height() << std::endl;
std::cout << "r1 new area: " << r1.area() << std::endl;

std::cout << "\n--- Creating r2 (invalid values for constructor) ---" << std::endl;
Rectangle r2(-2.0, 0.0);
std::cout << "r2 initial width: " << r2.get_width() << std::endl;
std::cout << "r2 initial height: " << r2.get_height() << std::endl;
std::cout << "r2 initial area: " << r2.area() << std::endl;

std::cout << "\n--- Attempting invalid set on r1 ---" << std::endl;
r1.set_width(-5.0);
std::cout << "r1 width after invalid set: " << r1.get_width() << std::endl;
std::cout << "r1 area after invalid width set: " << r1.area() << std::endl;

return 0;
}

当你运行这段代码时,你会看到 main 函数中的测试用例会按照预期工作,包括对无效输入的处理。


8. 扩展思考与进阶问题

为了帮助你更深入地理解和应用这些知识,可以思考以下问题:

  1. 更灵活的验证:当前的 Setter 在验证失败时会将值设为 1.0。如果希望调用者知道设置操作是否成功,Setter 可以返回一个 bool 值(例如 true 代表成功,false 代表失败)。你觉得这样做有什么好处和坏处?如何修改代码来实现这个功能?

    C++

    Code
    1
    2
    // 比如:
    // bool set_width(double new_width);
  2. 只读属性:如果一个成员变量(比如 id_)在对象创建后就不允许被修改,你该如何设计它的 Getter 和 Setter?(提示:可能不需要 Setter)

  3. 链式调用 (Method Chaining):有些编程风格喜欢链式调用 Setter,例如:rectangle.set_width(10.0).set_height(5.0);。为了实现这种效果,Setter 函数的返回类型应该是什么?(提示:通常返回对象的引用 *this)。尝试修改 set_widthset_height 以支持链式调用。

  4. 错误处理机制:在 Setter 中直接使用 std::cout 打印错误信息对于学习和简单程序是可行的。但在大型应用程序中,通常会使用更健壮的错误处理机制,比如抛出异常 (throwing exceptions)。你可以简单了解一下 C++ 中的 try-catchthrow 是如何工作的。

  5. Getter 返回引用:通常 Getter 按值返回成员变量的副本。在某些特定情况下,Getter 也可以返回成员变量的引用(例如 double& get_width())或常量引用(const double& get_width() const)。这样做有什么潜在的好处和风险?(提示:性能 vs. 封装性)。

这些问题可以帮助你从不同角度思考 Getters 和 Setters 的设计和使用。不要有压力,选择你感兴趣的一两个点去探索一下,会对你的 C++ 学习非常有益!

你今天的表现非常棒!继续保持这种学习的热情和细致的态度!如果对这些扩展问题有任何想法或者尝试了修改,欢迎随时与我分享和讨论。


评论